﻿// =====================================================================
//
//  This file is part of the Microsoft Dynamics CRM SDK code samples.
//
//  Copyright (C) Microsoft Corporation.  All rights reserved.
//
//  This source code is intended only as a supplement to Microsoft
//  Development Tools and/or on-line documentation.  See these other
//  materials for detailed information regarding Microsoft code samples.
//
//  THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//  PARTICULAR PURPOSE.
//
// =====================================================================
#region

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Net;
using System.Xml.Linq;
using Microsoft.Xrm.Sdk.XmlNamespaces;

#if SILVERLIGHT
using System.Net.Browser;
using Microsoft.Phone.Reactive;
#endif

#endregion

namespace Microsoft.Xrm.Sdk.Client.Protocols.WSDL
{
	public class WsdlReader
	{
		private readonly UriCollection _currentUrls = new UriCollection();
		private readonly WsdlDocuments _documents = new WsdlDocuments();
		private readonly object _lockObject = new object();
		private readonly UriCollection _newUrls = new UriCollection();
		private readonly UriCollection _processedUrls = new UriCollection();

		public WsdlDocuments Documents
		{
			get { return _documents; }
		}

		public void Retrieve(Uri serviceUri)
		{
			WsdlDocument document = new WsdlDocument(serviceUri);
			document.DocumentLoaded += document_DocumentLoaded;

			lock (_lockObject)
			{
				_currentUrls.Add(serviceUri);
				_documents.Add(serviceUri, document);
			}
			document.LoadDocument();
		}

		private void document_DocumentLoaded(object sender, WsdlDocumentCompletedEventArgs e)
		{
			if (e.WsdlLocations.Count > 0)
			{
				lock (_lockObject)
				{
					foreach (Uri uri in e.WsdlLocations)
					{
						if (!_newUrls.Contains(uri) && !_processedUrls.Contains(uri))
						{
							_newUrls.Add(uri);
						}
					}
				}
			}
			lock (_lockObject)
			{
				if (_currentUrls.Contains(e.ServiceUri))
				{
					//Remove the just processed document from the list of processing documents and add it to the processed list
					_currentUrls.Remove(e.ServiceUri);
					_processedUrls.Add(e.ServiceUri);
				}
				if (_newUrls.Count > 0)
				{
					//Fire off the retrieve for the next document.
					Uri uri = _newUrls[0];
					_newUrls.Remove(uri);
					Retrieve(uri);
				}
				else
				{
					if (_currentUrls.Count == 0)
					{
						MetadataLoadedEventArgs args = new MetadataLoadedEventArgs();
						args.Documents = this.Documents;
						OnMetadataLoaded(args);
					}
				}
			}
		}

		private void OnMetadataLoaded(MetadataLoadedEventArgs args)
		{
			if (MetadataLoaded != null)
			{
				MetadataLoaded(this, args);
			}
		}

		public event EventHandler<MetadataLoadedEventArgs> MetadataLoaded;
	}

	public class WsdlDocument
	{
		public WsdlDocument(Uri serviceUrl)
		{
			ServiceUri = serviceUrl;
		}

		public Uri ServiceUri { get; set; }
		public XDocument Document { get; set; }
		public Boolean Loaded { get; set; }
		public Exception Error { get; private set; }

		internal void LoadDocument()
		{
			HttpWebRequestState requestState = new HttpWebRequestState();

			requestState.ServiceUri = ServiceUri;

#if SILVERLIGHT
			requestState.Request = (HttpWebRequest)WebRequestCreator.ClientHttp.Create(ServiceUri);

			requestState.Request.Method = "GET";
			requestState.Request.AllowReadStreamBuffering = false;

			var observableRequest = Observable.FromAsyncPattern<WebResponse>(requestState.Request.BeginGetResponse, requestState.Request.EndGetResponse);
#else
            requestState.Request = (HttpWebRequest)WebRequest.Create(ServiceUri);

            requestState.Request.Method = "GET";
            requestState.Request.AllowReadStreamBuffering = false;

            // No-op for observable request. It is not used/invoked later in code.
#endif

			IAsyncResult state = requestState.Request.BeginGetResponse(BeginGetResponseCallback, requestState);
		}


		private void BeginGetResponseCallback(IAsyncResult result)
		{
			HttpWebRequestState requestState = (HttpWebRequestState)result.AsyncState;

			try
			{
				WebResponse response = requestState.Request.EndGetResponse(result);
				using (Stream responseStream = response.GetResponseStream())
				{
					using (StreamReader streamReader = new StreamReader(responseStream))
					{
						Document = XDocument.Load(streamReader);
						WsdlDocumentCompletedEventArgs args = CreateEventArgs(requestState.ServiceUri);
						LoadAdditionalLocations(args);
						Loaded = true;
						OnDocumentLoaded(args);
					}
				}
			}
			catch (Exception ex) // We do want to catch all exceptions and propagate all the way back to the application
			{
				WsdlDocumentCompletedEventArgs args = CreateEventArgs(requestState.ServiceUri);
				Loaded = false;
				Error = ex;
				OnDocumentLoaded(args);
			}
		}

		private WsdlDocumentCompletedEventArgs CreateEventArgs(Uri requestUri)
		{
			WsdlDocumentCompletedEventArgs args = new WsdlDocumentCompletedEventArgs();
			args.ServiceUri = requestUri;
			args.Document = this;
			return args;
		}

		private void LoadAdditionalLocations(WsdlDocumentCompletedEventArgs args)
		{
			IEnumerable<XElement> imports = from el in Document.Descendants(PublicNamespaces.WSDL + @"import")
											select el;


			foreach (XElement element in imports)
			{
				foreach (XAttribute attribute in element.Attributes())
				{
					if (attribute.Name == "location")
					{
						args.WsdlLocations.Add(new Uri(attribute.Value));
					}
				}
			}
		}

		private void OnDocumentLoaded(WsdlDocumentCompletedEventArgs args)
		{
			if (DocumentLoaded != null)
			{
				DocumentLoaded(this, args);
			}
		}

		public event EventHandler<WsdlDocumentCompletedEventArgs> DocumentLoaded;
	}

	public class WsdlDocumentCompletedEventArgs : EventArgs
	{
		private readonly UriCollection _wsdlLocations = new UriCollection();

		public UriCollection WsdlLocations
		{
			get { return _wsdlLocations; }
		}

		public Uri ServiceUri { get; set; }
		public WsdlDocument Document { get; set; }
	}

	public class MetadataLoadedEventArgs : EventArgs
	{
		public WsdlDocuments Documents { get; set; }
	}

	public class UriCollection : Collection<Uri>
	{
	}

	public class WsdlDocuments : Dictionary<Uri, WsdlDocument>
	{
	}

	public class HttpWebRequestState
	{
		public Uri ServiceUri { get; set; }
		public HttpWebRequest Request { get; set; }
	}
}
